﻿using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Nomo.UnityCoreModule
{
    /// <summary>
    /// 协程运行器。
    /// </summary>
    public static class CoroutineRunner
    {
        private sealed class CoroutineContainer : MonoBehaviour
        {
            private static CoroutineContainer _instance;

            internal static CoroutineContainer Instance
            {
                get
                {
                    if (_instance == null)
                    {
                        _instance = FindObjectOfType<CoroutineContainer>();
                        if (_instance == null)
                        {
                            DontDestroyOnLoad(_instance = new GameObject(nameof(CoroutineContainer)).AddComponent<CoroutineContainer>());
                        }
                    }
                    return _instance;
                }
            }
        }

        private sealed class CoroutineEnumerator : IEnumerator
        {
            private readonly IEnumerator _enumerator;
            private readonly Guid        _guid;

            internal event Action<CoroutineEnumerator, Guid> MoveToTheEnd;

            internal CoroutineEnumerator(IEnumerator enumerator, Guid guid, Action<CoroutineEnumerator, Guid> moveToTheEnd)
            {
                _enumerator  =  enumerator;
                _guid        =  guid;
                MoveToTheEnd += moveToTheEnd;
            }

            bool IEnumerator.MoveNext()
            {
                var moveNext = _enumerator.MoveNext();
                if (!moveNext)
                {
                    MoveToTheEnd?.Invoke(this, _guid);
                }
                return moveNext;
            }

            object IEnumerator.Current => _enumerator.Current;
            void IEnumerator.  Reset() => _enumerator.Reset();
        }

        private readonly struct CoroutineAndIEnumeratorTuple
        {
            internal readonly Coroutine   Coroutine;
            internal readonly IEnumerator Enumerator;

            internal CoroutineAndIEnumeratorTuple(Coroutine coroutine, IEnumerator enumerator)
            {
                Coroutine  = coroutine;
                Enumerator = enumerator;
            }
        }

        private static readonly Dictionary<Guid, Stack<CoroutineAndIEnumeratorTuple>> Dictionary = new Dictionary<Guid, Stack<CoroutineAndIEnumeratorTuple>>();

        /// <summary>
        /// 开启一个协程。
        /// </summary>
        /// <param name="enumerator">枚举器。</param>
        /// <param name="guid">对应这个协程的全局唯一标识符。</param>
        /// <returns>协程标记。</returns>
        /// <exception cref="ArgumentNullException"><paramref name="enumerator"/> 为 <see langword="null"/> .</exception>
        public static Coroutine Start(IEnumerator enumerator, out Guid guid)
        {
            if (enumerator == null)
            {
                throw new ArgumentNullException(nameof(enumerator));
            }
            guid = Guid.NewGuid();
            var coroutineEnumerator = new CoroutineEnumerator(enumerator, guid, OnCoroutineEnumeratorMoveToTheEnd);
            var coroutine           = CoroutineContainer.Instance.StartCoroutine(coroutineEnumerator);
            var stack               = new Stack<CoroutineAndIEnumeratorTuple>();
            stack.Push(new CoroutineAndIEnumeratorTuple(coroutine, coroutineEnumerator));
            Dictionary[guid] = stack;
            return coroutine;
        }

        private static void OnCoroutineEnumeratorMoveToTheEnd(CoroutineEnumerator coroutineEnumerator, Guid guid)
        {
            coroutineEnumerator.MoveToTheEnd -= OnCoroutineEnumeratorMoveToTheEnd;
            Dictionary.Remove(guid);
        }

        /// <summary>
        /// 在指定的全局唯一标识符对应的协程里开启一个子协程。
        /// </summary>
        /// <param name="enumerator">枚举器。</param>
        /// <param name="guid">父协程的全局唯一标识符。</param>
        /// <returns>协程标记。</returns>
        public static Coroutine Join(IEnumerator enumerator, Guid guid)
        {
            if (Dictionary.TryGetValue(guid, out var value))
            {
                var coroutine = CoroutineContainer.Instance.StartCoroutine(enumerator);
                value.Push(new CoroutineAndIEnumeratorTuple(coroutine, enumerator));
                return coroutine;
            }
            return default;
        }

        /// <summary>
        /// 停止由单个或多个全局唯一标识符对应的协程。
        /// </summary>
        /// <param name="guids">单个或多个全局唯一标识符。</param>
        public static void Stop(params Guid[] guids)
        {
            if (guids == null)
            {
                return;
            }
            foreach (var element in guids)
            {
                if (Dictionary.TryGetValue(element, out var value))
                {
                    while (value.Count != 0)
                    {
                        var pop = value.Pop();
                        StopCoroutine(pop.Coroutine);
                        StopCoroutine(pop.Enumerator);
                    }
                    Dictionary.Remove(element);
                }
            }
        }

        private static void StopCoroutine(Coroutine coroutine)
        {
            if (coroutine != null)
            {
                CoroutineContainer.Instance.StopCoroutine(coroutine);
            }
        }

        private static void StopCoroutine(IEnumerator enumerator)
        {
            while (enumerator != null)
            {
                CoroutineContainer.Instance.StopCoroutine(enumerator);
                object current;
                try
                {
                    current = enumerator.Current;
                }
                catch
                {
                    return;
                }
                enumerator = current as IEnumerator;
            }
        }
    }
}
